from __future__ import print_function ## a hack to enable print as a function. This MUST come as the first line.
import struct
import zlib
import logging

## This is an abstract class for all tables
class PuzaTable:
    PUZA_TABLE_MAGIC = 8501
    input_filename = ''
    output_filename = ''
    key = ''
    local_format = 0
    block_number = 1
    _local_buffer = []
    _inner_structure = {}
    _input_file_handler = None
    _output_file_handler = None
    _input_binary_buffer = []
    _output_binary_buffer = []
    EOF = False
    
    @staticmethod
    def CheckMagic(b):
        n = struct.unpack('i',b)[0]
        if n == PuzaTable.PUZA_TABLE_MAGIC:
            return True
        else:
            return False
    
    def __init__(self, key = '', block_number = 1):               
        self.key = key
        self.block_number = block_number
        self.EOF = False
        
    def Restart(self):
        self._local_buffer = []
        self._inner_structure = {}
        self._input_file_handler = None
        self._output_file_handler = None
        self._input_binary_buffer = []
        self._output_binary_buffer = []
        self.EOF = False
        
    @staticmethod      
    def TableFormat(filename):
        ## detect whether the table file is a text file or a binary file
        flag = 0 ## 0 is a text file, 1 is a binary file
        input_f = open(filename,'rb')
        c = input_f.read(4)
        k = struct.unpack('i',c)[0]
        if k == PuzaTable.PUZA_TABLE_MAGIC:
            ## this is a binary file
            flag = 1
        else:
            flag = 0
        return flag   

    def InputFile(self, filename = ''):
        self.input_filename = filename
        if PuzaTable.TableFormat(self.input_filename) == 1:
            logging.info('InputFile file format: Binary')        
            self.local_format = 1
        else:
            logging.info('InputFile file format: Text')            
            self.local_format = 0
        return
    
    def OutputFile(self, filename = ''):
        self.output_filename = filename
        return
    
    def StreamInRow(self):
        record_obj = None                    
        if self.input_filename != '' and self.EOF == False:
            ## stream in through a file
            record_obj = None
            if self.local_format == 1:
                record_obj = self._StreamInFromFileBinary()
            else:
                record_obj = self._StreamInFromFileText()
            if record_obj == '':
                record_obj = None
        return record_obj
        
    
    def _StreamInFromFileText(self):
        record_obj = None        
        if self._input_file_handler:
            record_obj = self._input_file_handler.readline().strip()
        else:
            if not self.EOF:
                ## the file is never opened
                input_f = open(self.input_filename, 'r')
                self._input_file_handler = input_f
                record_obj = self._input_file_handler.readline().strip()            
        if record_obj == '':
            self._input_file_handler.close()
            self.EOF = True
        return record_obj
    
    def _StreamInFromFileBinary(self):
        record_obj = None
        if not self._input_file_handler:
            input_f = open(self.input_filename,'rb')
            self._input_file_handler = input_f
            c = self._input_file_handler.read(4) ## skip the magic number
            self._input_binary_buffer = [] ## clear binary buffer
        if self._input_binary_buffer == []:
            if self.EOF == False:        
                c = self._input_file_handler.read(4)
                if c!= "":
                    l = struct.unpack('i',c)[0] ## read the block size
                    s = self._input_file_handler.read(l)
                    decom_s = zlib.decompress(s)
                    w = decom_s.strip().split('\n')
                    self._input_binary_buffer.extend(w)
                else:
                    self._input_file_handler.close()
                    self.EOF = True
        if self._input_binary_buffer != []:
            record_obj = self._input_binary_buffer.pop(0)
        return record_obj
    
    def StreamOutRow(self, output_obj = None, lformat = 0, block_number = 1):       
        print(self._output_file_handler) 
        if lformat == 1:
            self.local_format = 1
        self.block_number = block_number
        if output_obj != None:
            if self.local_format == 0:
                self._StreamOutToFileText(output_obj)
            else:
                self._StreamOutToFileBinary(output_obj)
    
    def StreamClose(self):
        if self._output_file_handler:
            if self.local_format == 1:
                if len(self._output_binary_buffer) > 0:
                    com_str = '\n'.join(self._output_binary_buffer)
                    c = zlib.compress(com_str)
                    l = len(c)
                    self._output_file_handler.write(struct.pack('i',l))
                    self._output_file_handler.write(c)
            self._output_file_handler.close()    
    
    def _StreamOutToFileText(self, output_obj):
        if not self._output_file_handler:
            print('create file')
            output_f = open(self.output_filename,'w')
            self._output_file_handler = output_f
        self._output_file_handler.write(output_obj.Dump()+'\n')        

    def _StreamOutToFileBinary(self, output_obj):
        if not self._output_file_handler:
            output_f = open(self.output_filename,'wb')
            output_f.write(struct.pack('i', self.PUZA_TABLE_MAGIC))
            self._output_file_handler = output_f
        self._output_binary_buffer.append(output_obj.Dump())
        if len(self._output_binary_buffer) >= self.block_number:
            com_str = '\n'.join(self._output_binary_buffer)
            c = zlib.compress(com_str)
            l = len(c)
            self._output_file_handler.write(struct.pack('i',l))
            self._output_file_handler.write(c)
            self._output_binary_buffer = []
        
    def Rows(self, start_n, end_n):
        return